package org.jing.ext.security;

import org.jing.core.lang.JingException;

import java.io.FileInputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;

/**
 * Description: 证书工具类 <br>
 *  · Linux环境下使用openssl req -new -x509 -days 5480 -keyout CA.pem -out CA.crt生成.pem私钥文件与.crt公钥文件 <br>
 * &nbsp;&nbsp;&nbsp;&nbsp;[-new: 生成新的证书请求] <br>
 * &nbsp;&nbsp;&nbsp;&nbsp;[-x509: 使用X.509规则] <br>
 * &nbsp;&nbsp;&nbsp;&nbsp;[-days 5480: 证书的有效期5480天(15年), 仅当使用了 -x509 选项后有效] <br>
 * &nbsp;&nbsp;&nbsp;&nbsp;[-keyout CA.key: 私钥文件名指定为CA.pem, 此选项的一般作用是新生成文件命名. 但若同时使用了-key选项, 则此选项用于原私钥文件的更名] <br>
 * &nbsp;&nbsp;&nbsp;&nbsp;[-out CA.crt: 指定输出所生成自签名证书的信息到文件，且文件名为CA.crt(建议不要省略)] <br>
 * &nbsp;&nbsp;&nbsp;&nbsp;其中，-days，-keyout 两个选项可以省略，省略的话使用默认值，有效期默认为 30 天(由程序内部在变量初始化的时候指定, 与配置文件无关),
 * 私钥文件名的默认值由配置文件 openssl.cnf 中相关条目指定，没改过的话为 privkey.pem <br>
 * &nbsp;&nbsp;&nbsp;&nbsp;选项 -out 若是省略的话, openssl不会以文件形式输出生成的 证书/证书请求, 而是会默认将文件的信息直接打印到屏幕上, 这在大多数情况下, 是不符合我们要求的. 所以建议这个选项最好不要省略 <br>
 * &nbsp;&nbsp;&nbsp;&nbsp;req子命令可以通过 -key 选项为证书请求指定使用一个已存在的私钥文件. 但在示例中的情况下, 虽然使用了-new 和 -x509两个选项, 但没有使用 -key 选项, 这时, req子命令会自动为自签名证书生成一个RSA私钥, 密钥长度的默认值由配置文件 openssl.cnf 中的相关条目指定, 没改过的话为 1024 bits. <br>
 * <br>
 * · Linux环境下使用openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key.pem -out private_key.der -nocrypt将.pem文件转换为.der私钥文件. <br>
 * @author bks <br>
 * @since 2021-07-19 <br>
 */
public class CertificateFile {
    /**
     * Description: 读取使用X.509规则生成的.crt证书的公钥. <br>
     * @param crtFilePath .crt证书路径 <br>
     * @return 公钥 <br>
     */
    public static String readPublicKeyStringFromX509CRT(String crtFilePath) {
        return Common.getBase64Encoder().encodeToString(readPublicKeyFromX509CRT(crtFilePath).getEncoded());
    }

    /**
     * Description: 读取使用X.509规则生成的.crt证书的公钥. <br>
     * @param crtFilePath .crt证书路径 <br>
     * @return 公钥 <br>
     */
    public static PublicKey readPublicKeyFromX509CRT(String crtFilePath) {
        try {
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            try (FileInputStream inputStream = new FileInputStream(crtFilePath)) {
                X509Certificate certificate = (X509Certificate) factory.generateCertificate(inputStream);
                return certificate.getPublicKey();
            }
        catch (JingException e) {
            throw e;
        }
        catch (Throwable t) {
                throw new JingException(t);
            }
        }
        catch (JingException e) {
            throw e;
        }
        catch (Throwable t) {
            throw new JingException(t);
        }
    }

    /**
     * Description: 使用X.509规则生成的.crt证书进行加密. <br>
     *
     * @param crtFilePath 证书路径 <br>
     * @param content 待加密内容 <br>
     * @param charset 字符集 <br>
     * @return 加密字符串 <br>
     */
    public static String encryptByX509CRT(String crtFilePath, String content, String charset) {
        return RSABase64.encryptByPublicKey(content, charset, readPublicKeyStringFromX509CRT(crtFilePath));
    }

    public static byte[] decryptByX509CRT(String crtFilePath, byte[] buffer) {
        PublicKey publicKey = readPublicKeyFromX509CRT(crtFilePath);
        return RSABase64.decryptByPublicKey(buffer, publicKey);
    }

    /**
     * Description: 读取使用X.509规则生成的.keystore证书的私钥. <br>
     * @param jksFilePath jks文件路径 <br>
     * @param keyAlias 密钥名 <br>
     * @param keyPassword 密钥密码 <br>
     * @param storePassword jsk文件密码 <br>
     * @return 私钥 <br>
     */
    public static String readPrivateKeyFromX509JavaKeyStore(String jksFilePath, String keyAlias, String keyPassword, String storePassword) {
        try (FileInputStream inputStream = new FileInputStream(jksFilePath)) {
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(inputStream, storePassword.toCharArray());
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyPassword.toCharArray());
            return Common.getBase64Encoder().encodeToString(privateKey.getEncoded());
        }
        catch (JingException e) {
            throw e;
        }
        catch (Throwable t) {
            throw new JingException(t);
        }
    }

    /**
     * Description: 使用X.509规则生成的.keystore证书进行解密. <br>
     * @param jksFilePath jks文件路径 <br>
     * @param keyAlias 密钥名 <br>
     * @param keyPassword 密钥密码 <br>
     * @param storePassword jsk文件密码 <br>
     * @param content 待解密内容 <br>
     * @param charset 字符集 <br>
     * @return 解密字符串 <br>
     */
    public static String decryptByX509JavaKeyStore(String jksFilePath, String keyAlias, String keyPassword, String storePassword, String content, String charset) {
        return RSABase64.decryptByPrivateKey(content, charset, readPrivateKeyFromX509JavaKeyStore(jksFilePath, keyAlias, keyPassword, storePassword));
    }

    /**
     * Description: 读取使用X.509规则生成的.pem文件转换后的.der证书. <br>
     * @param derFilePath der密钥文件路径 <br>
     * @return 私钥 <br>
     */
    public static String readPrivateKeyStringFromX509DER(String derFilePath) {
        try {
            return Common.getBase64Encoder().encodeToString(readPrivateKeyFromX509DER(derFilePath).getEncoded());
        }
        catch (JingException e) {
            throw e;
        }
        catch (Throwable t) {
            throw new JingException(t);
        }
    }

    public static PrivateKey readPrivateKeyFromX509DER(String derFilePath) {
        try {
            byte[] bytes = Files.readAllBytes(Paths.get(derFilePath));
            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePrivate(spec);
        }
        catch (JingException e) {
            throw e;
        }
        catch (Throwable t) {
            throw new JingException(t);
        }
    }

    /**
     * Description: 使用X.509规则生成的.pem文件转换后的.der证书进行解密. <br>
     * @param derFilePath der密钥文件路径 <br>
     * @param content 待解密内容 <br>
     * @param charset 字符集 <br>
     * @return 解密字符串 <br>
     */
    public static String decryptByX509DER(String derFilePath, String content, String charset) {
        return RSABase64.decryptByPrivateKey(content, charset, readPrivateKeyStringFromX509DER(derFilePath));
    }

    public static byte[] decryptByX509DER(String derFilePath, byte[] buffer) {
        PrivateKey privateKey = readPrivateKeyFromX509DER(derFilePath);
        return RSABase64.decryptByPrivateKey(buffer, privateKey);
    }
}
